#include "stdafx.h"
#include "Direct3D9.h"
#include "..\Common\Model.h"
#include "..\Window.h"
#include "..\..\Global\globaldef.h"

#ifdef BUILD_D3D9
#pragma comment( lib, "d3dx9.lib" )
#pragma comment( lib, "d3d9.lib" )

LPDIRECT3DDEVICE9			CD3D9::g_pd3dDevice = NULL;
LPDIRECT3D9					CD3D9::g_pD3D = NULL;
LPD3DXEFFECT				CD3D9::g_pCurrentShader = NULL;
LPD3DXSPRITE				CD3D9::g_pSpriteBatch = NULL;
LPD3DXFONT					CD3D9::g_pd3dxFont = NULL;
bool						CD3D9::g_SpriteBatchActive = false;

/**
	Releases a graphics resource. This function assumes that any data allocated for the resource
	being released was created with a device instance that matches this implementation.

	@author Dacre Denny
	@param pResource pointer to the address of the resource being deleted
	@return true if successful otherwise false
*/
bool CD3D9::ReleaseResource(Render::tResource** pResource) {

	if(!pResource)
		return false;

	if(!*pResource)
		return false;

	FOREACH(Render::tResource*, i, Render::g_Resources) {

		Render::tResource* pIt = *i;
		if(pIt == *pResource) {

			RELEASE_D3D9_RESOURCE(pIt->pResource)
			Render::g_Resources.erase(i);
			delete *pResource;
			*pResource = NULL;
			return true;
		}
	}

	return false;
}

/**
	Releases a shader. This function assumes that the shader being released was created 
	with a device instance that matches this device implementation.

	@author Dacre Denny
	@param pResource pointer to resource being released
	@return true if successful otherwise false
*/
bool CD3D9::ReleaseShader(Render::tShader* pShader) {

	if(!pShader)
		return false;

	FOREACH(Render::tShader*, i, Render::g_Shaders) {

		Render::tShader* pIt = *i;
		if(pIt == pShader) {

			if(Render::UpdateShaderRefCount(pShader, -1) == 0) {

				RELEASE_D3D9_RESOURCE(pIt->pEffect);
				RELEASE_D3D9_RESOURCE(pIt->pInputDeclaration);
				Render::g_Shaders.erase(i);
				delete pShader;
			}
			return true;
		}
	}

	return false;
}

bool CD3D9::GetTexture(const std::string& filename, Render::tResource** pOut = NULL) {

	return false;
}

/**
	Creates a texture for this device based on the specified parameters. This function will search
	for an existing match of the data specified and return that if found. It will otherwise attempt
	to create a new texture.

	@author Dacre Denny
	@param filename the filepath to the textures data source
	@param pOut optional pointer to assign the texture object to
	@param pDevice optional pointer to device that owns this texture
	@return true if successful otherwise false
*/
bool CD3D9::CreateTexture(const std::string& filename, Render::tResource** pOut, LPVOID pDevice) {

	Render::tResource* pTexture = NULL;

	VTRY {

		if(filename.length() == 0)
			VTHROW_INTERNALERROR("Precondition failed");

		for(std::vector<Render::tResource*>::iterator i = Render::g_Resources.begin(); 
			i != Render::g_Resources.end(); i++) {

			Render::tResource* pIt = *i;
			if(pIt->pResource) {

				if(AS_D3D9_RESOURCE(pIt->pResource)->GetType() == D3DRTYPE_TEXTURE && filename.compare(pIt->filename) == 0) {
					*pOut = pIt;
					return true;
				}
			}
		}
		
		pTexture = new Render::tResource();
		if(!pTexture)
			VTHROW_INTERNALERROR("Memory error");
		
		pTexture->pResource	= NULL;
		pTexture->filename	= filename;
		pTexture->type		= Render::TEXTURE;
		
		switch(D3DXCreateTextureFromFile(pDevice ? LPDIRECT3DDEVICE9(pDevice) : CD3D9::g_pd3dDevice, filename.c_str(), (LPDIRECT3DTEXTURE9*)&pTexture->pResource)) {
			case D3D_OK:
				break;
			case E_OUTOFMEMORY:
			case D3DERR_OUTOFVIDEOMEMORY:
				VTHROW_INTERNALERROR("Memory error");
			default:
				VTHROW_INTERNALERROR("D3D9 error");
		}

		if(pOut)
			*pOut = pTexture;

		Render::g_Resources.push_back(pTexture);
		return true;

	}VCATCHONLY;

	if(pTexture) {
		if(pTexture->pResource)
			AS_D3D9_RESOURCE(pTexture->pResource)->Release();
		delete pTexture;
	}

	if(pOut) {
		if(*pOut)
			*pOut = NULL;
	}

	return false;
}

/**
	Dynamically builds a shader for this device based on the specified parameters, namely the features
	parameter. This function will search for an existing match of the data specified and return that if 
	found. It will otherwise attempt to build a new shader that consists of the features extracted from
	the features flag.

	@author Dacre Denny
	@param pSourceShader the shader of which the newly assembled shader is being based. Do not use this pointer after the function call
	@param featureSet the flag(s) containing information about the features required in the reassembled shader
	@param pOut pointer of location where resulting shader is stored if the function is successful
	@return true if successful otherwise false
*/
/*bool CD3D9::ReassmbleShader(Render::tShader* const pSourceShader, u_int featureSet, Render::tShader** pOut) {

	VTRY {

		if(!pSourceShader || !pOut)
			VTHROW_INTERNALERROR("Precondition failed");

		//Handle case where user has probably passed in the same shader
		if(pSourceShader->features == featureSet) {
			*pOut = pSourceShader;
			return true;
		}
	
		FOREACH(Render::tShader*, i, Render::g_Shaders) {

			Render::tShader *pElement = (*i);
			if(	pElement->features == featureSet &&
				pElement->filename.compare(pSourceShader->filename) == 0 &&
				pElement->inputFormat == pSourceShader->inputFormat ) 
			{
				Render::UpdateShaderRefCount(pElement, +1);
				*pOut = pElement;

				if(Render::UpdateShaderRefCount(pSourceShader, -1) == 0)
					if(!ReleaseShader(pSourceShader))
						VTHROW_INTERNALERROR("Failed to release shader");

				return true;
			}
		}

		if(!CreateShader(pSourceShader->filename, pSourceShader->inputFormat, featureSet, pOut))
			VTHROW_INTERNALERROR("Shader creation failed");
		
		return true;
	}
	VCATCH;

	return false;
}*/

/**
	Creates a shader for this device based on the specified parameters. This function will search for 
	an existing match of the data specified and return that if found. It will otherwise attempt to creat 
	a new shader.

	@author Dacre Denny
	@param filename file path of shader source
	@param ShaderType type
	@param featureSet optional feature set for this shader
	@param pInOut optional pointer for external storage. This device still owns the shader is a valid pointer is provided. If the pointer provided is valid it will be replace
	@param forceReload option flag to indicate if the renderer should reparse the shader file
	@param pDevice optional pointer to device that will own this shader
	@return true if successful otherwise false
*/
bool CD3D9::CreateShader(const std::string& filename, const uint inputFormat, const u_int featureSet, Render::tShader** pInOut, bool forceReload, LPVOID pTargetDevice) {

	LPDIRECT3DDEVICE9 pDevice	= pTargetDevice ? LPDIRECT3DDEVICE9(pTargetDevice) : CD3D9::g_pd3dDevice;
	LPD3DXEFFECT pEffect		= NULL;
	LPD3DXBUFFER pBuffer		= NULL;
	Render::tShader* pShader	= NULL;
	LPDIRECT3DVERTEXDECLARATION9 pVertexDeclaration = NULL;

	VTRY {

		if(filename.length() == 0)
			VTHROW_INTERNALERROR("Precondition failed");

		const std::string filePath = Render::SHADER_DIRECTORY + filename;

		uint uptoByte = 0;
		uint UVUsageIndex = 1;	//tangent uses idx 0
		std::vector<D3DVERTEXELEMENT9> elements;
		std::vector<tStringPair> macros;
		macros.push_back(tStringPair(std::string("D3D9_IMPL"), std::string("1")));

		for(uint i = 0; i < VERTEX_ELEMENT_COUNT; i++) {
		
			const uint featureMask = 1 << i;
			if(inputFormat & featureMask) {
			
				uint byteInc = 0;
				char buffer[128] = {'\0'};
				const char* element = vertex_elements[i];	
				sprintf_s(buffer, 128, "%s", element);
				macros.push_back(tStringPair(std::string(buffer), "1"));

				D3DVERTEXELEMENT9 e;
				e.Stream = 0;
				e.Offset = sizeof(float) * uptoByte;
				e.Method = D3DDECLMETHOD_DEFAULT;
				e.UsageIndex = 0;

				switch(featureMask) {
					case VERTEX_POSITION:
					case VERTEX_NORMAL:
					{
						byteInc = 3;
						e.Type = D3DDECLTYPE_FLOAT3;
						e.Usage =	(VERTEX_POSITION & featureMask) ? D3DDECLUSAGE_POSITION : 
									(VERTEX_NORMAL & featureMask) ? D3DDECLUSAGE_NORMAL : -1;
						break;
					}
					case VERTEX_TANGENT:
					{
						byteInc = 3;
						e.Type = D3DDECLTYPE_FLOAT3;
						e.Usage =	(VERTEX_TANGENT & featureMask) ? D3DDECLUSAGE_TEXCOORD : -1;
						e.UsageIndex = 0;
						break;
					}
					case VERTEX_UV0: case VERTEX_UV1: case VERTEX_UV2:
					{
						byteInc = 2;
						e.Type = D3DDECLTYPE_FLOAT2;
						e.Usage = D3DDECLUSAGE_TEXCOORD;
						e.UsageIndex = UVUsageIndex;
						UVUsageIndex++;
						break;
					}
					case VERTEX_COLOR:
					{
						byteInc = 4;
						e.Type = D3DDECLTYPE_FLOAT4;
						e.Usage = D3DDECLUSAGE_COLOR;
						e.UsageIndex = 0;
						break;
					}
					case VERTEX_DEFORMATION:
					{
						byteInc = 1;
						e.Type = D3DDECLTYPE_FLOAT1;
						e.Usage = D3DDECLUSAGE_TEXCOORD;
						e.UsageIndex = 5;
						UVUsageIndex++;
						break;
					}
					default:
					{
						continue;
						break;
					}
				}

				elements.push_back(e);
				uptoByte += byteInc;
			}
		}

		D3DVERTEXELEMENT9 e = D3DDECL_END();
		elements.push_back(e);		

		if(!Render::ComplieShaderMacroList(featureSet, &macros))
			VTHROW_INTERNALERROR("Failed to compile shader macros");

		std::vector<D3DXMACRO> defines;
		FOREACH(tStringPair, i, macros) {

			D3DXMACRO shader_macro = {i->m_macro.c_str(), i->m_define.c_str()};
			defines.push_back(shader_macro);
		}

		D3DXMACRO shader_macro = {NULL, NULL};
		defines.push_back(shader_macro);

		if(FAILED(pDevice->CreateVertexDeclaration(&elements[0], &pVertexDeclaration)))
			VTHROW_INTERNALERROR("Failed to create vertex declaration");

		switch(D3DXCreateEffectFromFile(pDevice, filePath.c_str(), &defines[0], NULL, D3DXSHADER_DEBUG, NULL, 
			&pEffect, &pBuffer)) {
				case D3D_OK:
					break;
				case E_OUTOFMEMORY:
					VTHROW_INTERNALERROR("Memory error");
				default:
					if(pBuffer) {

						char errorBuffer[512] = {'\n'};
						memcpy(errorBuffer, pBuffer->GetBufferPointer(), 512 - 1);
						VTHROW_INTERNALERROR(errorBuffer);
					} 
					else {
						VTHROW_INTERNALERROR("D3D9 error");
					}
		}
	
		if(pInOut)
			if(*pInOut) {

				pShader = *pInOut;
				RELEASE_D3D9_RESOURCE(pShader->pEffect);
				RELEASE_D3D9_RESOURCE(pShader->pInputDeclaration);
			}

		if(!pShader) {

			FOREACH(Render::tShader*, i, Render::g_Shaders) {

				Render::tShader *pElement = (*i);
				if(	pElement->filename.compare(filePath) == 0 &&
					pElement->inputFormat == inputFormat && 
					pElement->features == featureSet) {

					pShader = *i;
					RELEASE_D3D9_RESOURCE(pShader->pEffect);
					RELEASE_D3D9_RESOURCE(pShader->pInputDeclaration);
					break;
				}
			}
			
			if(!pShader) {
				pShader = new Render::tShader();
				Render::g_Shaders.push_back(pShader);
				if(pInOut)
					*pInOut = pShader;
			}
		}

		pShader->features		= featureSet;
		pShader->filename		= filename;
		pShader->pEffect		= pEffect;
		pShader->pInputDeclaration = pVertexDeclaration;
		pShader->inputFormat	= inputFormat;
		Render::UpdateShaderRefCount(pShader, 1);
	
		return true;
	
	}VCATCHONLY;
	
	if(pBuffer)
		pBuffer->Release();
	
	if(pVertexDeclaration)
		pVertexDeclaration->Release();

	if(pShader)
		ReleaseShader(pShader);
	else if(pEffect)
		pEffect->Release();

	return false;
}

/**
	Creates this device instance. If successful, the device will be in a state that is suitable 
	for rendering

	@author Dacre Denny
	@param hWnd handle to the host/parent window
	@return true if successful otherwise false
*/
bool CD3D9::Create(HWND hWnd) {

    D3DDISPLAYMODE d3ddm;
	D3DPRESENT_PARAMETERS d3dpp;
	RECT windowClientArea;
	LPDIRECT3DDEVICE9 pd3dDevice = NULL;
	LPDIRECT3D9 pD3D = NULL;
	LPD3DXSPRITE	pSpriteBatch = NULL;
	LPD3DXFONT		pd3dxFont = NULL;

	VTRY {

		if(!hWnd)
			VTHROW_INTERNALERROR("Bad parameter.");

		pD3D = Direct3DCreate9( D3D_SDK_VERSION );
		if(!pD3D)
			return false;

		if(FAILED(pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm )))
			VTHROW_INTERNALERROR("Failed to get display mode.");

		ZeroMemory( &d3dpp, sizeof(d3dpp) );
		d3dpp.Windowed               = TRUE;
		d3dpp.SwapEffect             = D3DSWAPEFFECT_COPY;
		d3dpp.BackBufferFormat       = d3ddm.Format;
		d3dpp.EnableAutoDepthStencil = TRUE;
		d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
		d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;

		if(FAILED(pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
							  D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pd3dDevice)))
			VTHROW_INTERNALERROR("Failed to create device.");
		
		if(!GetWindowRect(hWnd, &windowClientArea))
			VTHROW_INTERNALERROR("");
 
		float width = float(windowClientArea.right - windowClientArea.left);
		float height = float(windowClientArea.bottom - windowClientArea.top);
		if(height == 0.0f)
			height = 1.0f;

		SetCameraProjection(D3DXToRadian( 70.0f ), width / height, 0.1f, 100.0f);
		SetCameraFrameNavigation(D3DXVECTOR3(0.0f,0.0f,0.0f), 0.0f, 0.0f);
	
		if(FAILED(D3DXCreateSprite( pd3dDevice, &pSpriteBatch)))
			return false;
	
		HDC hDC;
		int nHeight;
		int nPointSize = 8;

		hDC = GetDC( NULL );

		nHeight = -( MulDiv( nPointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72 ) );

		ReleaseDC( NULL, hDC );


		if(FAILED(D3DXCreateFont( pd3dDevice, nHeight, 0, FW_NORMAL, 0, FALSE, 
			DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 
			DEFAULT_PITCH | FF_DONTCARE, TEXT("Arial"), &pd3dxFont )))
			VTHROW_INTERNALERROR("");

		CD3D9::Release();

		if(!Render::Singleton()->CreateTexture("basedata.bmp", &Render::g_pProceduralBaseData, pd3dDevice))
			VTHROW("Failed to create procedural base data");

		CD3D9::g_Camera.m_Forward = D3DXVECTOR3(0.0f,0.0f,1.0f);
		CD3D9::g_Camera.m_Right = D3DXVECTOR3(1.0f,0.0f,0.0f);
		CD3D9::g_Camera.m_Angles[0] = 0.0f;
		CD3D9::g_Camera.m_Angles[1] = 0.0f;
	
		Render::g_pDefaultShader	= NULL;
		CD3D9::g_pd3dDevice			= pd3dDevice;
		CD3D9::g_pD3D				= pD3D;
		CD3D9::g_pSpriteBatch		= pSpriteBatch;
		CD3D9::g_pd3dxFont			= pd3dxFont;

		return true;

	}
	VCATCHONLY;

	RELEASE_D3D9_RESOURCE(pSpriteBatch);
	RELEASE_D3D9_RESOURCE(pd3dxFont);

	RELEASE_D3D9_RESOURCE(pd3dDevice);
	RELEASE_D3D9_RESOURCE(pD3D);
	CD3D9::Release();

	return false;
}

bool CD3D9::Release() {
	
	FOREACH(Render::tResource*, i, Render::g_Resources) {

		Render::tResource* pIt = *i;
		if(pIt) {
			RELEASE_D3D9_RESOURCE(pIt->pResource);
			delete pIt;
		}
	}
	Render::g_Resources.clear();

	FOREACH(Render::tShader*, i, Render::g_Shaders) {

		tShader* pIt = *i;
		if(pIt) {
			RELEASE_D3D9_RESOURCE(pIt->pEffect);
			delete pIt;
		}
	}
	Render::g_Shaders.clear();

	RELEASE_D3D9_RESOURCE(CD3D9::g_pSpriteBatch);
	RELEASE_D3D9_RESOURCE(CD3D9::g_pd3dxFont);

	/*RELEASE_D3D9_RESOURCE(CD3D9::g_VD_Common);
	RELEASE_D3D9_RESOURCE(CD3D9::g_VD_Simple);*/

	RELEASE_D3D9_RESOURCE(CD3D9::g_pd3dDevice);
	RELEASE_D3D9_RESOURCE(CD3D9::g_pD3D);

	CD3D9::g_SpriteBatchActive = false;

	return true;
}

/**
	Reloads all currently buffered resources under the currently active device implmenetation. The function assumes that if
	device data for a resource already exists then that data is for this device implementation.

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool CD3D9::ReloadResources() {

	VTRY
	{
		FOREACH(Render::tResource*, i, Render::g_Resources) {

			Render::tResource* pIt = *i;
			if(pIt->pResource) {

				LPDIRECT3DRESOURCE9 pNewResource = NULL;
				switch(AS_D3D9_RESOURCE(pIt->pResource)->GetType()) {

					case D3DRTYPE_TEXTURE: 
						{
							if(FAILED(D3DXCreateTextureFromFile(CD3D9::g_pd3dDevice, pIt->filename.c_str(), (LPDIRECT3DTEXTURE9*)&pNewResource)))
								VTHROW_INTERNALERROR("Failed to reload texture (D3D9)");
						}break;
					case D3DRTYPE_CUBETEXTURE: 
						{
							if(FAILED(D3DXCreateCubeTextureFromFile(CD3D9::g_pd3dDevice, pIt->filename.c_str(), (LPDIRECT3DCUBETEXTURE9*)&pNewResource)))
								VTHROW_INTERNALERROR("Failed to reload cube texture (D3D9)");
						}break;
					default:
						{
							continue;
						}break;
				}

				RELEASE_D3D9_RESOURCE(pIt->pResource);
				pIt->pResource = pNewResource;
			}
		}

		FOREACH(Render::tShader*, i, Render::g_Shaders) {

			tShader* pIt = *i;
			if(!CreateShader(pIt->filename, pIt->inputFormat, pIt->features, &pIt)) {
				VTHROW_INTERNALERROR("Failed to reload shader (D3D9)");
			}
			else
				Render::UpdateShaderRefCount(pIt, -1);	//Must do this because the CreateShader automatically increments the count - not something we want it to do here

			pIt->Print();
		}

		printf("------------------\nTotal Shaders:%i\n", Render::g_Shaders.size());

		return true;
	}
	VCATCHONLY;
		
	return false;
}

/**
	Renders a batch of triangles that has been bound to the device via a vertex buffer.
	This implementation of the Render class offers no support for instancing and therefore
	the second parameter has no effect

	@author Dacre Denny
	@param elementCount the number of triangle to be drawn
	@param pInstanceCount has no effect
	@return true if successful otherwise false
*/
bool CD3D9::DrawTriangleList(UINT elementCount, UINT* pInstanceCount) {

	if(elementCount == 0)
		return false;

	return SUCCEEDED(CD3D9::g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0, elementCount));
}

bool CD3D9::DrawIndexedTriangleList(UINT triangelCount, UINT* pInstanceCount) {

	MessageBox(0,0,0,0);
	return false;
}

/**
	Renders a batch of lines that have been bound to the device via a vertex buffer.

	@author Dacre Denny
	@param elementCount the number of line segments to be drawn
	@return true if successful otherwise false
*/
bool CD3D9::DrawLineList(UINT elementCount) {

	if(elementCount == 0)
		return false;

	return SUCCEEDED(CD3D9::g_pd3dDevice->DrawPrimitive(D3DPT_LINELIST,0, elementCount));
}

bool CD3D9::CreateVertexIndicieBuffer(UINT sizeBytes,LPVOID pData,  uint vertexFormat, tResource** pVBOut, tResource** pIBOut) {

	if(!pData || !pVBOut || !pIBOut || sizeBytes == 0 || (vertexFormat & VERTEX_POSITION) == 0)
		return false;

	MessageBox(0,"Todo","",0);
	return true;
}

bool CD3D9::BindStreamOutBuffer(tResource* pStreamOutBuffer, UINT elementSize) {

	return false;
}
	
bool CD3D9::BindIndexBuffer(tResource* pIndexBuffer, UINT elementSize) {

	MessageBox(NULL,"Unimplemented: BindIndexBuffer()", "Error", NULL);
	return false;
}
bool CD3D9::BindVertexBuffer(tResource** pVertexBuffers, UINT* elementSizes, UINT bufferCount) {

	if(!pVertexBuffers || !elementSizes || bufferCount == 0)
		return false;

	for(UINT i = 0; i < bufferCount; i++) {

		tResource* pVertexBuffer = pVertexBuffers[i];

		if(!pVertexBuffer || elementSizes[i] == 0)
			return false;

		if(!pVertexBuffer->pResource)
			return false;

		if(AS_D3D9_RESOURCE(pVertexBuffer->pResource)->GetType() != D3DRTYPE_VERTEXBUFFER)
			return false;

		if(FAILED(CD3D9::g_pd3dDevice->SetStreamSource( 0, LPDIRECT3DVERTEXBUFFER9(pVertexBuffer->pResource), 0, elementSizes[i])))
			return false;
	}

	return true;
}

bool CD3D9::FillVertexBuffer(tResource* pVertexBuffer, UINT sizeInBytes, LPVOID pData) {

	if(!pData || !pVertexBuffer || sizeInBytes == 0)
		return false;

	LPDIRECT3DRESOURCE9 pResource = (LPDIRECT3DRESOURCE9)pVertexBuffer->pResource;
	if(D3DRTYPE_VERTEXBUFFER != pResource->GetType())
		return false;

	LPDIRECT3DVERTEXBUFFER9 pD3D9VertexBuffer = (LPDIRECT3DVERTEXBUFFER9)pResource;
	D3DVERTEXBUFFER_DESC bufferDesc;
	if(FAILED(pD3D9VertexBuffer->GetDesc(&bufferDesc)))
		return false;

	UINT dataSize = min(bufferDesc.Size, sizeInBytes);

	LPVOID pVertices = NULL;
	if(SUCCEEDED(pD3D9VertexBuffer->Lock( 0, dataSize, (void**)&pVertices, 0 ))) {
		memcpy(pVertices, pData, dataSize);
		
		if(FAILED(pD3D9VertexBuffer->Unlock()))
			return false;
	}

	return true;
}

bool CD3D9::GetBufferInfo(tResource* pBuffer, UINT* pSizeInBytes) {

	MessageBox(NULL,"Unimplemented: GetBufferInfo()", "Error", NULL);
	return false;
}

bool CD3D9::CreateIndexBuffer(UINT sizeBytes, tResource** pInOut) {

	MessageBox(NULL,"Unimplemented: CreateIndexBuffer()", "Error", NULL);
	return false;
}

/**
	Creates a vertex buffer resource under this device. If the resource that is specified already 
	exists as a buffer resource then it is assumed to have been created from an instance of a device 
	of this type. It will be recreated to satisfy the input parameters.

	@author Dacre Denny
	@param sizeBytes size of the resulting buffer
	@param isStreamOut indicates if this buffer will be used to store streamed shader data
	@param pInOut pointer to address of desitnation resource
	@return true if successful otherwise false
*/
bool CD3D9::CreateVertexBuffer(UINT sizeBytes, bool isStreamOut, Render::tResource** pInOut) {

	LPDIRECT3DVERTEXBUFFER9 pNewBuffer = NULL;
	Render::tResource* newResource = NULL;

	VTRY
	{
		if(!pInOut || sizeBytes == 0)
			VTHROW_INTERNALERROR("Precondition failed");

		if(isStreamOut)
			VTHROW_INTERNALERROR("D3D9 does not support stream out / geometry shaders");

		switch((CD3D9::g_pd3dDevice->CreateVertexBuffer(sizeBytes, 0, 0, D3DPOOL_DEFAULT, &pNewBuffer, NULL))) {

			case D3D_OK:
				break;
			case D3DERR_OUTOFVIDEOMEMORY:
			case E_OUTOFMEMORY:
				VTHROW_INTERNALERROR("Memory error");
			default:
				VTHROW_INTERNALERROR("D3D9 error");
		}
		
		if(*pInOut) {

			if(!(LPDIRECT3DVERTEXBUFFER9)(*pInOut)->pResource) {

				(*pInOut)->pResource = pNewBuffer;
			}
			else {

				FOREACH(Render::tResource*, i, Render::g_Resources) {

					Render::tResource* pIt = *i;
					if(!pIt)
						continue;

					if((LPDIRECT3DVERTEXBUFFER9)(*pInOut)->pResource != pIt->pResource)
						continue;
		
					RELEASE_D3D9_RESOURCE(pIt->pResource);
					pIt->pResource = pNewBuffer;
					break;
				}
			}
		}
		else {
			
			newResource = new Render::tResource();
			if(!newResource)
				VTHROW_INTERNALERROR("Memory error");

			newResource->type		= Render::VERTEX_BUFFER;
			newResource->pResource	= pNewBuffer;
			Render::g_Resources.push_back(newResource);
			if(pInOut)
				*pInOut = newResource;
		}

		return true;
	}
	VCATCHONLY;

	RELEASE_D3D9_RESOURCE(pNewBuffer);
	if(newResource)
		delete newResource;

	return false;
}


bool CD3D9::GetTextureInfo(tResource* pTexture, UINT* pWidth, UINT* pHeight) {

	if(!pTexture)
		return false;

	if(AS_D3D9_RESOURCE(pTexture->pResource)->GetType() != D3DRTYPE_TEXTURE)
		return false;

	LPDIRECT3DTEXTURE9 pD3D9Texture = LPDIRECT3DTEXTURE9(pTexture->pResource);

	D3DSURFACE_DESC info;
	if(FAILED(pD3D9Texture->GetLevelDesc(0, &info)))
		return false;

	if(pWidth)
		*pWidth = info.Width;
	if(pHeight)
		*pHeight = info.Height;

	return true;
}

bool CD3D9::StreamOutBuffer(StreamedMesh* pParentMesh, Render::tResource* pInstanceBuffer, UINT* pPrimitvesWritten) {

	return false;
}

void CD3D9::SetShaderTexture(DWORD Stage, tResource* pTexture) {
	
	if(Stage < 0 || Stage > (16 - 1) || !CD3D9::g_pCurrentShader)
		return;

	char b[8] = {'\0'};
	sprintf_s(b,8,"g_tex%i",Stage);
	
	if(pTexture) {
		D3DRESOURCETYPE type = ((LPDIRECT3DRESOURCE9)pTexture->pResource)->GetType();
		if(!(type == D3DRTYPE_TEXTURE || type == D3DRTYPE_CUBETEXTURE))
			return;
		CD3D9::g_pCurrentShader->SetTexture(b, LPDIRECT3DBASETEXTURE9(pTexture->pResource));

	} else {
		CD3D9::g_pCurrentShader->SetTexture(b, NULL);
	}

}

void CD3D9::SetProcedural(DWORD Stage,tProceduralData* pProcedural) {

	//if(Stage < 0 || Stage > (16 - 1) || !CD3D9::g_pCurrentShader || !pProcedural)
	//	return;

	//char* pBytes = &pProcedural->procedural[0];
	//switch((PROCEDURAL_FORMAT)pProcedural->proceduralFormat) {

	//	case PERLIN_2D: {
	//		float amplitude = READ_PROCEDURAL(float, pBytes);
	//		float frequency = READ_PROCEDURAL(float, pBytes);
	//		u_int octaves = READ_PROCEDURAL(u_int, pBytes);

	//		CD3D9::g_pCurrentShader->SetFloat("g_perlin2d.amplitude", amplitude);
	//		CD3D9::g_pCurrentShader->SetFloat("g_perlin2d.frequency", frequency);
	//		CD3D9::g_pCurrentShader->SetInt("g_perlin2d.octaves", octaves);
	//					}break;
	//	default:
	//		break;
	//}
	//if(!pProcedural)

}

void CD3D9::BeginRendering() {
	
	if(CD3D9::g_pd3dDevice) {

		CD3D9::g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_COLORVALUE(0.10f,0.10f,0.10f,1.0f), 1.0f, 0 );
		CD3D9::g_pd3dDevice->BeginScene();
	}
}

void CD3D9::EndRendering() {

	if(CD3D9::g_pd3dDevice) {

		CD3D9::DrawGrid();
		CD3D9::g_pd3dDevice->EndScene();
		RECT dest;
		CWindow::GetClientRectangle(&dest);
		CD3D9::g_pd3dDevice->Present( NULL, &dest, NULL, NULL );
	}
}

bool CD3D9::SetShaderFloat(LPCSTR pName, float value) {

	if(!pName || !CD3D9::g_pCurrentShader)
		return false;

	return SUCCEEDED(CD3D9::g_pCurrentShader->SetFloat(pName, value));
}

bool CD3D9::SetShaderInt(LPCSTR pName, int value) {

	if(!pName || !CD3D9::g_pCurrentShader)
		return false;

	return SUCCEEDED(CD3D9::g_pCurrentShader->SetInt(pName, value));
}

bool CD3D9::SetShaderMatrix(LPCSTR pName, D3DXMATRIX* pValue) {

	if(!pName || !pValue || !CD3D9::g_pCurrentShader)
		return false;

	return SUCCEEDED(CD3D9::g_pCurrentShader->SetMatrix(pName, pValue));
}

bool CD3D9::SetShaderVector(LPCSTR pName, D3DXVECTOR4* pValue) {

	if(!pName || !pValue || !CD3D9::g_pCurrentShader)
		return false;

	return SUCCEEDED(CD3D9::g_pCurrentShader->SetVector(pName, pValue));
}

bool CD3D9::BeginShader(const Render::tShader* pShader, bool isInstanced, UINT* pPasses) {

	if(!pShader)
		return false;

	if(CD3D9::g_pCurrentShader)	{
		CD3D9::g_pCurrentShader->EndPass();
		CD3D9::g_pCurrentShader->End();
	}

	if(FAILED(CD3D9::g_pd3dDevice->SetVertexDeclaration((LPDIRECT3DVERTEXDECLARATION9)(pShader->pInputDeclaration))))
		return false;

	CD3D9::g_pCurrentShader = (LPD3DXEFFECT) pShader->pEffect;
	
	if(SUCCEEDED(CD3D9::g_pCurrentShader->Begin(pPasses, 0))) {
			
		D3DXMATRIX matrixViewProjection;
		Render::Singleton()->GetCameraViewProjection(&matrixViewProjection);
		Render::Singleton()->SetShaderMatrix( "ViewProj", &matrixViewProjection );
		Render::Singleton()->SetShaderVector( "ViewPosition", &D3DXVECTOR4(Render::g_Camera.m_Position, 0.0f));
		return true;
	} 
	else
		return false;
}

bool CD3D9::EndShader() {

	if(!CD3D9::g_pCurrentShader)
		return false;

	CD3D9::g_pCurrentShader->EndPass();
	CD3D9::g_pCurrentShader->End();
	CD3D9::g_pCurrentShader = NULL;
	return true;

}

bool CD3D9::BeginSprites() {

	if(FAILED(CD3D9::g_pSpriteBatch->Begin(D3DXSPRITE_ALPHABLEND)))
		return false;

	g_SpriteBatchActive = true;
	return true;
}

bool CD3D9::EndSprites() {

	if(FAILED(CD3D9::g_pSpriteBatch->End()))
		return false;

	g_SpriteBatchActive = false;
	return true;
}

void CD3D9::DrawText(std::string& text,DWORD type, D3DCOLOR color, RECT* pSrc) {

	if(text.length() > 0)
		CD3D9::g_pd3dxFont->DrawText( NULL,text.c_str(), -1, pSrc, DT_NOCLIP, color);
}

void CD3D9::DrawSprite(tResource* pTexture, D3DXVECTOR2& position, D3DXVECTOR2& scale, RECT* pSrc) {

	if(!pTexture || !CD3D9::g_SpriteBatchActive)
		return;

	if(AS_D3D9_RESOURCE(pTexture->pResource)->GetType() != D3DRTYPE_TEXTURE)
		return;

	D3DXMATRIX mat_scale;
	D3DXMatrixScaling(&mat_scale, scale.x, scale.y, 1.0f);

	D3DXMATRIX mat_translate;
	D3DXMatrixTranslation(&mat_translate, position.x, position.y, 1.0f);
	
	D3DXMATRIX transform = mat_translate * mat_scale;

	CD3D9::g_pSpriteBatch->SetTransform(&transform);
	CD3D9::g_pSpriteBatch->Draw(LPDIRECT3DTEXTURE9(pTexture->pResource), pSrc, NULL, NULL, D3DCOLOR_RGBA(255,255,255,255));
}

bool CD3D9::BeginShaderPass(UINT pass) {

	if(!CD3D9::g_pCurrentShader)
		return false;
	CD3D9::g_pCurrentShader->BeginPass(pass);
	return true;
}

bool CD3D9::EndShaderPass() {

	if(!CD3D9::g_pCurrentShader)
		return false;
	CD3D9::g_pCurrentShader->EndPass();
	return true;
}

#endif